/*
Copyright (c) 2010, Chin Huang
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.ll19.webapp.jsp;

import java.beans.FeatureDescriptor;
import java.util.Iterator;

import javax.el.ELContext;
import javax.el.ELResolver;
import javax.servlet.jsp.JspContext;

/**
 * {@link javax.el.ELResolver} which escapes XML in String values.
 * 
 * 
 * ELResolver
 * 
 * 能够自定义 EL 表达式求值的变量和属性解析行为。 对一个表达式进行求值时，参考与 ELContext 关联的
 * ELResolver，以对表达式的第一个变量进行初始解析。遇到 . 或 []
 * 操作符时也要参考它，但方法表达式中最后一个这样的操作符除外，因为在这种情况下解析规则是固定编码的。
 * 
 * 例如，在 EL 表达式 ${employee.lastName} 中，ELResolver 确定 employee 引用的对象，以及获取该对象
 * lastName 属性的含义。
 * 
 * 此类中的大多数方法都接受 base 和 property 参数。如果是变量解析（例如，确定 ${employee.lastName} 中 employee
 * 引用的内容），则 base 参数将为 null，property 参数将总是为 String 类型。这种情况下，如果 property 不是
 * String，则 ELResolver 的行为未定义。
 * 
 * 如果是属性解析，则 base 参数标识 base 对象，property 对象标识 base 上的属性。例如，在表达式
 * ${employee.lastName} 中，base 是 employee 变量解析的结果，property 是字符串 "lastName"。在表达式
 * ${y[x]} 中，base 是 y 变量解析的结果，property 是 x 变量解析的结果。
 * 
 * 尽管只有一个 ELResolver 与 ELContext 关联，但对于任何给定的变量或属性解析，通常可以使用多个解析器。ELResolver 使用
 * CompositeELResolver 连接到一起，以定义丰富语义来计算表达式。
 * 
 * 对于 #getValue、#getType、#setValue 和 #isReadOnly 方法，ELResolver 不负责解析所有可能的 (base,
 * property) 对。实际上，大部分解析器将仅能处理一种类型的 base。要指示解析器成功地解析特定 (base, property) 对，它必须将
 * ELContext 的 propertyResolved 属性设置为 true。如果它无法处理给定对，则必须保持此属性不变。如果
 * propertyResolved 为 false，则调用者必须忽略该方法的返回值。
 * 
 * 设计 #getFeatureDescriptors 和 #getCommonPropertyType
 * 方法的主要目的是支持设计时工具，但它们也必须在运行时处理调用。可以使用
 * 
 * Error:null
 * 
 * 
 * 
 */
public class EscapeXmlELResolver extends ELResolver {

	/**
	 * pageContext attribute name for flag to enable XML escaping
	 */
	public static final String ESCAPE_XML_ATTRIBUTE = EscapeXmlELResolver.class
			.getName() + ".escapeXml";

	private ThreadLocal<Boolean> excludeMe = new ThreadLocal<Boolean>() {
		@Override
		protected Boolean initialValue() {
			return Boolean.FALSE;
		}
	};

	@Override
	public Class<?> getCommonPropertyType(ELContext context, Object base) {
		return null;
	}

	@Override
	public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
			Object base) {
		return null;
	}

	@Override
	public Class<?> getType(ELContext context, Object base, Object property) {
		return null;
	}

	@Override
	public Object getValue(ELContext context, Object base, Object property) {
		JspContext pageContext = (JspContext) context
				.getContext(JspContext.class);
		Boolean escapeXml = (Boolean) pageContext
				.getAttribute(ESCAPE_XML_ATTRIBUTE);
		if (escapeXml != null && !escapeXml) {
			return null;
		}

		try {
			if (excludeMe.get()) {
				return null;
			}

			// This resolver is in the original resolver chain. To prevent
			// infinite recursion, set a flag to prevent this resolver from
			// invoking the original resolver chain again when its turn in the
			// chain comes around.
			excludeMe.set(Boolean.TRUE);
			Object value = context.getELResolver().getValue(context, base,
					property);

			if (value instanceof String) {
				value = EscapeXml.escape((String) value);
			}
			return value;

		} finally {
			excludeMe.set(Boolean.FALSE);
		}
	}

	@Override
	public boolean isReadOnly(ELContext context, Object base, Object property) {
		return false;
	}

	@Override
	public void setValue(ELContext context, Object base, Object property,
			Object value) {

	}
}
